Android IPC 通信之 AIDL

上一小节我们讲了 Binder 基础。现在我们继续讲一下 Android 中的 IPC --- AIDL 。

在 Android 的开发中,Binder 使用比较多是在 Service 中,像 AIDL 和 Messenger 使用的还是比较少的。但是普通 Service 中的 Binder 不涉及跨进程通信,比较简单,也不触及 Binder 核心,而 Messenger 的底层实现是 AIDL ,所以我们先讲一下 AIDL。

记得之前刚学 Android 的时候,AIDL 也只是听过,也没有实际操作过,甚至对于 AIDL 是干什么的,印象还是很模糊。今天也算接触了。

在这里引用一下 《Android 开发艺术探索》 中的例子,进行 AIDL 的分析,当然,分析的内容也是从这本书中学来的。。。。

首先,我们先创建一个实体对象 Book ,并实现 Parcelable 序列化接口。

Book

public class Book implements Parcelable {
    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName){
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}

注意一点,由于对象在 IPC 过程中,会进行序列化和反序列化,所以最后获取到的对象不是同一个对象,即使两次传的是同一个对象,两端之间都不能识别。

创建 AIDL 文件

然后就开始写 aidl 文件了。我们可以手动在 main 目录下,也就是跟 java 同级的目录,新建 aidl 文件夹,然后新建跟当前包名相同的包名文件夹,然后才在这个文件夹下,新建 aidl 文件。

但是,我们用的是什么? Android Studio 啊! 这么智能的集成开发环境,当然不用这么繁琐的操作,只需要在文件夹右击,选择新建 aidl 文件 就可以了。是不是很牛皮。

Book.aidl

这个 aidl 的作用主要是用来序列化 Book 这个实体。

package com.gzyx.demo;
parcelable Book;

IBookManager

这个 aidl 文件主要是定义了接口,其中 getBookList() 用来获取书籍、addBook(in Book book) 用来添加书籍

package com.gzyx.demo;

import com.gzyx.demo.Book; //即使在同一个包下也要进行导包!

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

AIDL 中,除了基本数据类型,其他类型的参数必须标上方向:in 、 out 或者 inout 。

然后,我们重新 Build 一下工程,然后在 本Module 的 build - generated - source - aidl - debug - 包名 的路径下就可以找到生成的 java 文件了。

我们看一下目录结构:

其实我们可以看到生成的这个类其实还是一个接口,还继承了 IInterface 接口。


package com.gzyx.demo;

public interface IBookManager extends android.os.IInterface {
    public java.util.List<com.gzyx.demo.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.gzyx.demo.Book book) throws android.os.RemoteException;

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.gzyx.demo.IBookManager {
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        private static final java.lang.String DESCRIPTOR = "com.gzyx.demo.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.gzyx.demo.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.gzyx.demo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.gzyx.demo.IBookManager))) {
                return ((com.gzyx.demo.IBookManager) iin);
            }
            return new com.gzyx.demo.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.gzyx.demo.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.gzyx.demo.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.gzyx.demo.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.gzyx.demo.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.gzyx.demo.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.gzyx.demo.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.gzyx.demo.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.gzyx.demo.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
    }
}

主要分为这几个部分:


- IBookManager(接口,需要通信的接口)
    |
    └ Stub(静态抽象内部类,继承了Binder,抽象实现了 IBookManager,真正实现是在 Server 中)
        |
        └─ Proxy(静态内部类,实现了 IBookManager,主要是用来被 Client 进行调用,然后发起 Binder 通信)
        |
        └─ asInterface(方法,返回 Proxy 对象,Client 通过这个方法获取到 Proxy 对象)
        |
        └─ asBinder(方法,获取Binder,返回的是 Stub 对象本身)
        |
        └─ onTransact(方法,客户端发起 IPC 通信后,服务端接收到消息后在这个方法中进行处理,然后调用 Server 中实际的逻辑处理,处理完毕后,向 reply 中写入返回值,如果这个方法返回 false,客户端会请求失败,我们可以用来做权限控制)
        |
        └─ DESCRIPTOR (静态字符串,这个是 Binder 的唯一标识)

过程大概是这样子的

还有一点要注意的是,当客户端发起远程 IPC 请求时,当前线程会被挂起直到服务端返回结果,所以这个过程可能是很耗时的,所以最好不要在 UI 线程中发起远程 IPC 。而 服务端的 Binder 是 运行在 Binder 线程池中的。

分析完 AIDL 文件生成的 java 文件,也大概了解了AIDL 的调用过程,那我们就开始写 Server 端代码了。

Server 端

BookManagerService

public class BookManagerService extends Service {
    private ArrayList<Book> mBookList = new ArrayList<>();
    private Binder mBinder = new IBookManager.Stub(){
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    public BookManagerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG", "---->远程服务启动");
        mBookList.add(new Book(1, "book1"));
        mBookList.add(new Book(2, "book2"));
    }
}

这就如我们之前分析的一样, Server 主要就是继承 Stub ,然后实现 AIDL 接口。

记得要注册 Service ,设置 action 。

<service
    android:name=".BookManagerService">
    <intent-filter>
        <action android:name="com.gzyx.demo.AIDL_SERVICE"></action>
    </intent-filter>
</service>

Client 端

由于要模拟跨进程通信,所以我们需要新建一个 project 或者一个 module 。

又或者可以在 Manifest 中把 服务端的服务 跑在另一个进程中,使用 process 就可以了。

客户端要做的事情就简单多了,只要绑定服务,获取到 Binder 然后发送请求就可以了。

但是我们首先要把服务端的 aidl 整个文件夹拷贝到 客户端 中相同的位置。这是为了保证 aidl 文件的一致性。

public class MainActivity extends AppCompatActivity {

    private ServiceConnection conn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
        final Book book = new Book(12334, "book12334");
    }
    private void start(){
        Intent intent = new Intent("com.gzyx.demo.AIDL_SERVICE");
        intent.setPackage("com.gzyx.demo");// 5.0 以上要显式启动 Server ,需要设置这个
        if(conn == null){
            conn = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    IBookManager bookManager = IBookManager.Stub.asInterface(service);
                    try {
                        // 添加书籍
                        bookManager.addBook(new Book(3, "book3"));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }

                    try {
                        // 获取书籍
                        List<Book> bookList = bookManager.getBookList();
                        for (Book book : bookList) {
                            Log.e("TAG", "---->book:" + book.toString());
                        }
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }

                }

                @Override
                public void onServiceDisconnected(ComponentName name) {

                }
            };
        }

        // 绑定服务
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(conn);
        super.onDestroy();
    }
}

这部分跟我们之前分析的也一样,就只是 bindService ,然后通过 ServiceConnection 获取到 Binder ,再通过 Stub.asInterface() ,获取到 Proxy 代理对象,然后通过这个代理对象发起 IPC 。

启动后,如果有 log 输出,就说明没有问题了。

到这里,简单的 AIDL 就完成了。其实 AIDL 是方便我们进行 IPC 通信,其实底层是使用 Binder ,但是却把跟 Binder 通信相关的东西全都封装在 AIDL 生成的 java 文件里面了。使我们开发者不需要关心这一部分的操作。

《Android 开发艺术探索》 关于这一章还有一些内容,比如是验证、监听等一些操作,这里就不拓展了。

# IPC  AIDL 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×